﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using FarseerPhysics.SamplesFramework;
using Flatland.AgentBehavior;
using Flatland.Elements;
using Flatland.GeneticProgramming.CoreTypes;
using Microsoft.Xna.Framework;

/**
* 
* Copyright (c) 2012 Anders Høst Kjærgaard | ahkj@itu.dk
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction, 
* including without limitation the rights to use, copy, modify, merge, publish, distribute, 
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial 
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

namespace Flatland.Labs {
    /// <summary>
    /// Base class for labs. Flatlab encapsualtes the functionality for evaluating a Genotype with an Agent.
    /// Subclass must override ResetEnvironment.
    /// So far complex behaviours cannot be evaluated.
    /// </summary>
    public class FlatlandLab {
        /// <summary>
        /// Evaluation time for each agent - game-time ticks.
        /// </summary>
        public const int DEFAULT_EVALUATION_TIME = 60 * 5;

        public const float BORDERS_DIST_TO_EDGES = 0.1f;

        private readonly int _individualEvaluationTime;

        /// <summary>
        /// Reference to Flatland
        /// </summary>
        public FlatlandWorld Flatland;

        /// <summary>
        /// Instance of GP that will evolve the genotype.
        /// </summary>
        private GeneticProgramming.GP _gp;

        /// <summary>
        /// True during a run of the PG
        /// </summary>
        private bool GPisRunning;

        /// <summary>
        /// The agent we are evaluating
        /// </summary>
        public Agent Agent { get; set; }

        /// <summary>
        /// Report for the GenoType currnetly being evaulated with the Agent.
        /// </summary>
        private Report _agentReport;

        /// <summary>
        /// Current time a given agent has been evaluated
        /// </summary>
        private int _evaluationTime;

        /// <summary>
        /// Current individual being evaluated
        /// </summary>
        private int _individualsEvaluated;

        /// <summary>
        /// Start position of the agent.
        /// </summary>
        private readonly Vector2 _agentStartPosition = new Vector2(0f, 0f);

        /// <summary>
        /// Flatland has an outer border.
        /// </summary>
        public List<Border> Borders { get; set; }

        /// <summary>
        /// Use public constructor instead.
        /// </summary>
        protected FlatlandLab() { }


        /// <summary>
        /// Construct a FlatlandLab. Make it run by calling its update method from the main game loop.
        /// </summary>
        public FlatlandLab(GeneticProgramming.GP gp, int individualEvaluationTime = DEFAULT_EVALUATION_TIME) {
            //Setup GP run for step-wise evolution
            _gp = gp;
            _gp.EvaluateFitness = false;
            _gp.Init();
            _individualEvaluationTime = individualEvaluationTime;
            GPisRunning = true;
        }

        protected virtual void ResetEnvironment() {

        }

        /// <summary>
        /// Creates an Agent and adds the behaviour.
        /// </summary>
        private void SetupTestAgent() {
            if(Agent != null)
                Agent.RemoveAgent();
            Agent = new Agent();
            _agentReport = _gp.Population[_individualsEvaluated];
            Agent.Behavior = _agentReport.GenoType;
            Agent.InitAgent(_agentStartPosition, 3);
            Agent.Body.Position = _agentStartPosition;
            Behavior.BehaviorFactory(Agent, _agentReport);
            //Agent.Control = true;
            ResetEnvironment();
        }

        /// <summary>
        /// Respond will update the Agent currently being trained, and insert a new one when evaluation time is over.
        /// </summary>
        /// <param name="gameTime"></param>
        public virtual void Update(GameTime gameTime) {
            if (!GPisRunning) {
                Console.WriteLine("Lab GP run has terminated");
                Flatland.Exit();
                return;
            }
            if (Agent == null) {
                SetupTestAgent();
            }
            Debug.Assert(Agent != null, "Agent != null");

            //Training time is over. Set the fitness and get a new Agent
            if (_evaluationTime == _individualEvaluationTime) {
                _agentReport.Fitness = _agentReport.GenoType.Fitness();
                _evaluationTime = 0;
                ++_individualsEvaluated;
                if (_individualsEvaluated == _gp.Population.Count) {
                    GPisRunning = _gp.Evolve();
                    _individualsEvaluated = 0;
                }
                SetupTestAgent();
            }
            Agent.Update();
            ++_evaluationTime;
        }


        public void Draw() {
            //foreach (Border b in Borders) {
            //    b.Draw();
            //}
            Agent.Draw();
        }

        public virtual void LoadContent() {
            //float indent = BORDERS_DIST_TO_EDGES;
            //int x = Flatland.GraphicsDeviceManager.PreferredBackBufferWidth / 2;
            //int y = Flatland.GraphicsDeviceManager.PreferredBackBufferHeight / 2;
            //Borders = new List<Border>(4);
            ////Upper left to upper right
            //Borders.Add(new Border(Flatland, ConvertUnits.ToSimUnits(new Vector2(indent - x, indent - y)), ConvertUnits.ToSimUnits(new Vector2(x - indent, indent - y))));
            ////Upper right to lower right
            //Borders.Add(new Border(Flatland, ConvertUnits.ToSimUnits(new Vector2(x - indent, indent - y)), ConvertUnits.ToSimUnits(new Vector2(x - indent, y - indent))));
            ////Lower right to lower left
            //Borders.Add(new Border(Flatland, ConvertUnits.ToSimUnits(new Vector2(x - indent, y - indent)), ConvertUnits.ToSimUnits(new Vector2(indent - x, y - indent))));
            ////Lower left to upper right
            //Borders.Add(new Border(Flatland, ConvertUnits.ToSimUnits(new Vector2(indent - x, y - indent)), ConvertUnits.ToSimUnits(new Vector2(indent - x, indent - y))));
        }
    }
}
